/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/ib/mgt/ibmf/ibmf_saa_impl.h>
#include <sys/ib/mgt/ibmf/ibmf_saa_utils.h>

extern	saa_state_t	*saa_statep;
extern	int	ibmf_trace_level;

static void
ibmf_saa_informinfo_cb(void *arg, size_t length, char *buffer,
    int status, uint32_t producer_type);

static int
ibmf_saa_send_informinfo(saa_port_t *saa_portp, uint32_t producer_type,
    boolean_t subscribe, boolean_t unseq_unsubscribe);

static void
ibmf_saa_notify_event_client_task(void *args);

static void
ibmf_saa_process_subnet_event(saa_port_t *saa_portp, ib_mad_notice_t *notice);

/*
 * ibmf_saa_subscribe_events:
 * Subscribe or unsubscribe to subnet events for a certain port.
 * ibmf_saa_subscribe_events() will send an InformInfo request for each of the
 * four notice producer types.
 *
 * Subscribes generally occur when the first client for a port opens a session
 * and when a port with registered ibmf_saa clients transitions to active.
 * Subscribes are done as asynchronous, sequenced transactions.
 *
 * ibmf_saa sends unsubscribe requests when the last client for a port
 * unregisters and when an CI_OFFLINE message is received from ibtf (via ibmf).
 * For the first case, the unsubscribe is done as an asynchronous, sequenced
 * transaction.  For the second case, the request is asynchronous, unsequenced.
 * This means that the unsubscribes will not be retried.  Because the port is
 * going away we cannot wait for responses.  Unsubscribes are not required
 * anyway as the SA will remove subscription records from ports it determines to
 * be down.
 *
 * For subscribe requests, clients are notified that the request failed through
 * the event notification mechanism.  For unsubscribe requests,  clients are not
 * notified if the request fails.  Therefore, this function returns void.
 *
 * Input Arguments
 * saa_portp		pointer to port state structure
 * subscribe		B_TRUE if request is a Subscribe, B_FALSE if unsubscribe
 * unseq_unsubscribe	B_TRUE if unsubscribe request should be unsequenced
 * 			(called from CI_OFFLINE event handler)
 * 			B_FALSE if sequenced (wait for response) or for all
 *			subscribe requests
 *
 * Output Arguments
 * none
 *
 * Returns
 * void
 */
void
ibmf_saa_subscribe_events(saa_port_t *saa_portp, boolean_t subscribe,
    boolean_t unseq_unsubscribe)
{
	int				res;
	ibmf_saa_event_details_t	event_details;
	boolean_t			notify_clients = B_FALSE;
	uint8_t				success_mask;

	IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
	    ibmf_saa_subscribe_events_start, IBMF_TNF_TRACE, "",
	    "ibmf_saa_subscribe_events() enter\n");

	/* subscribes should always be sychronous */
	ASSERT((subscribe == B_FALSE) || (unseq_unsubscribe == B_FALSE));

	/*
	 * reset the arrive and success masks to indicate no responses have come
	 * back; technically only used for subscriptions but reset the values
	 * anyway
	 */
	mutex_enter(&saa_portp->saa_pt_event_sub_mutex);

	success_mask = saa_portp->saa_pt_event_sub_success_mask;

	saa_portp->saa_pt_event_sub_arrive_mask = 0;
	saa_portp->saa_pt_event_sub_success_mask = 0;

	mutex_exit(&saa_portp->saa_pt_event_sub_mutex);

	/*
	 * now subscribe/unsubscribe for each of the notice producer types;
	 * send_informinfo returns 1 on success, 0 on failure.  If the "or" of
	 * all four results is 0 then none of the informinfo's succeed and we
	 * should notify the client.  If it's not 0, then informinfo_cb will be
	 * called at least once, taking care of notifying the clients that there
	 * was a failure.
	 * For each producer type, send the request only if it's a subscribe or
	 * if it's an unsubscribe for a subscribe which succeeded
	 */

	/*
	 * subscribe for all traps generated by the SM;
	 * gid in service/out of service, mgid created/deleted, etc.
	 */
	if ((success_mask & IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_SM) ||
	    (subscribe == B_TRUE))
		res = ibmf_saa_send_informinfo(saa_portp,
		    MAD_INFORMINFO_NODETYPE_SUBNET_MANAGEMENT, subscribe,
		    unseq_unsubscribe);

	/* subscribe for all traps generated by a CA */
	if ((success_mask & IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_CA) ||
	    (subscribe == B_TRUE))
		res |= ibmf_saa_send_informinfo(saa_portp,
		    MAD_INFORMINFO_NODETYPE_CA, subscribe, unseq_unsubscribe);

	/* subscribe for all traps generated by a switch */
	if ((success_mask & IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_SWITCH) ||
	    (subscribe == B_TRUE))
		res |= ibmf_saa_send_informinfo(saa_portp,
		    MAD_INFORMINFO_NODETYPE_SWITCH, subscribe,
		    unseq_unsubscribe);

	/* subscribe for all traps generated by a router */
	if ((success_mask & IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_ROUTER) ||
	    (subscribe == B_TRUE))
		res |= ibmf_saa_send_informinfo(saa_portp,
		    MAD_INFORMINFO_NODETYPE_ROUTER, subscribe,
		    unseq_unsubscribe);

	/* if none of the subscribe requests succeeded notify the clients */
	if ((res == 0) && (subscribe == B_TRUE)) {

		IBMF_TRACE_1(IBMF_TNF_NODEBUG, DPRINT_L1,
		    ibmf_saa_subscribe_events_err, IBMF_TNF_ERROR, "",
		    "ibmf_saa_subscribe_events: %s\n", tnf_string, msg,
		    "Could not subscribe for any of the four producer types");

		mutex_enter(&saa_portp->saa_pt_event_sub_mutex);

		/* all events should have "arrived" */
		ASSERT(saa_portp->saa_pt_event_sub_arrive_mask ==
		    IBMF_SAA_PORT_EVENT_SUB_ALL_ARRIVE);

		/* status mask should be 0 since all failed */
		ASSERT(saa_portp->saa_pt_event_sub_success_mask == 0);

		/* notify clients if success mask changed */
		if (saa_portp->saa_pt_event_sub_last_success_mask !=
		    saa_portp->saa_pt_event_sub_success_mask)
			notify_clients = B_TRUE;

		/* update last mask for next set of subscription requests */
		saa_portp->saa_pt_event_sub_last_success_mask =
		    saa_portp->saa_pt_event_sub_arrive_mask = 0;

		mutex_exit(&saa_portp->saa_pt_event_sub_mutex);

		mutex_enter(&saa_portp->saa_pt_mutex);

		/*
		 * Sending the four InformInfos is treated as one port client
		 * reference.  Now that all have returned decrement the
		 * reference count.
		 */
		ASSERT(saa_portp->saa_pt_reference_count > 0);
		saa_portp->saa_pt_reference_count--;

		mutex_exit(&saa_portp->saa_pt_mutex);
	}

	/*
	 * for unsequenced unsubscribes, decrement the reference count here
	 * since no callbacks will ever do it
	 */
	if (unseq_unsubscribe == B_TRUE) {

		mutex_enter(&saa_portp->saa_pt_mutex);

		/*
		 * Sending the four InformInfos is treated as one port client
		 * reference.  Now that all have returned decrement the
		 * reference count.
		 */
		ASSERT(saa_portp->saa_pt_reference_count > 0);
		saa_portp->saa_pt_reference_count--;

		mutex_exit(&saa_portp->saa_pt_mutex);
	}

	if (notify_clients == B_TRUE) {

		bzero(&event_details, sizeof (ibmf_saa_event_details_t));

		ibmf_saa_notify_event_clients(saa_portp, &event_details,
		    IBMF_SAA_EVENT_SUBSCRIBER_STATUS_CHG, NULL);
	}

	IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4, ibmf_saa_subscribe_events_end,
	    IBMF_TNF_TRACE, "", "ibmf_saa_subscribe_events() exit\n");
}

/*
 * ibmf_saa_send_informinfo:
 *
 * Sends an InformInfo request to the SA.  There are two types of request,
 * Subscribes and Unsubscribes.  This function is called from
 * ibmf_saa_subscribe_events.  See that function's comment for usage of
 * subscribe, unseq_unsubscribe booleans.
 *
 * This function generates a standard ibmf_saa transaction and sends using
 * ibmf_saa_impl_send_request().  For asynchronous callbacks, the function
 * ibmf_saa_informinfo_cb() will be called.
 *
 * This function blocks allocating resources, but not waiting for response
 * packets.
 *
 * Input Arguments
 * saa_portp		pointer to port data
 * producer_type	InformInfo producer type to subscribe for
 * subscribe		B_TRUE if subscribe request, B_FALSE if unsubscribe
 * unseq_unsubscribe	B_TRUE if unsubscribe request should be unsequenced
 *			(called from CI_OFFLINE event handler)
 * 			B_FALSE if sequenced (wait for response) or for all
 *			subscribe requests
 *
 * Output Arguments
 * none
 *
 * Returns
 * 1 if the transaction succeeded, 0 if it failed
 */
static int
ibmf_saa_send_informinfo(saa_port_t *saa_portp, uint32_t producer_type,
    boolean_t subscribe, boolean_t unseq_unsubscribe)
{
	ib_mad_informinfo_t	inform_info;
	saa_impl_trans_info_t	*trans_info;
	int			res;
	uint8_t			producer_type_mask;

	IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
	    ibmf_saa_send_informinfo_start, IBMF_TNF_TRACE, "",
	    "ibmf_saa_send_informinfo() enter\n");

	IBMF_TRACE_4(IBMF_TNF_DEBUG, DPRINT_L3, ibmf_saa_send_informinfo,
	    IBMF_TNF_TRACE, "", "ibmf_saa_send_informinfo: %s, producer_type ="
	    "%x, subscribe = %x, unseq_unsubscribe = %x\n",
	    tnf_string, msg, "Sending informinfo request",
	    tnf_opaque, producer_type, producer_type,
	    tnf_int, subscribe, subscribe,
	    tnf_int, unseq_unsubscribe, unseq_unsubscribe);

	bzero(&inform_info, sizeof (ib_mad_informinfo_t));

	/* initialize inform_info packet */
	inform_info.LIDRangeBegin = MAD_INFORMINFO_ALL_ENDPORTS_RANGE;
	inform_info.IsGeneric = MAD_INFORMINFO_FORWARD_GENERIC;

	if (subscribe == B_TRUE)
		inform_info.Subscribe = MAD_INFORMINFO_SUBSCRIBE;
	else {
		inform_info.Subscribe = MAD_INFORMINFO_UNSUBSCRIBE;
		inform_info.QPN = saa_portp->saa_pt_qpn;
	}

	inform_info.Type = MAD_INFORMINFO_TRAP_TYPE_FORWARD_ALL;
	inform_info.TrapNumber_DeviceID =
	    MAD_INFORMINFO_TRAP_NUMBER_FORWARD_ALL;
	inform_info.ProducerType_VendorID = producer_type;

	trans_info = kmem_zalloc(sizeof (saa_impl_trans_info_t), KM_SLEEP);

	/* no specific client associated with this transaction */
	trans_info->si_trans_client_data = NULL;
	trans_info->si_trans_port = saa_portp;
	trans_info->si_trans_method = SA_SUBN_ADM_SET;
	trans_info->si_trans_attr_id = SA_INFORMINFO_ATTRID;
	trans_info->si_trans_component_mask = 0;
	trans_info->si_trans_template = &inform_info;
	trans_info->si_trans_template_length = sizeof (ib_mad_informinfo_t);
	trans_info->si_trans_unseq_unsubscribe = unseq_unsubscribe;

	/*
	 * if this isn't an unsequenced unsubscribe (the only synchronous
	 * request) then set up the callback
	 */
	if (unseq_unsubscribe == B_FALSE) {
		trans_info->si_trans_sub_callback =
		    ibmf_saa_informinfo_cb;
		trans_info->si_trans_callback_arg = saa_portp;

		/*
		 * if this is a subscribe, set the producer type so we can know
		 * which one's failed
		 */
		if (subscribe == B_TRUE) {
			trans_info->si_trans_sub_producer_type = producer_type;
		}
	}

	mutex_enter(&saa_portp->saa_pt_kstat_mutex);

	IBMF_SAA_ADD32_KSTATS(saa_portp, outstanding_requests, 1);
	IBMF_SAA_ADD32_KSTATS(saa_portp, total_requests, 1);

	mutex_exit(&saa_portp->saa_pt_kstat_mutex);

	res = ibmf_saa_impl_send_request(trans_info);
	if (res != IBMF_SUCCESS) {

		IBMF_TRACE_2(IBMF_TNF_NODEBUG, DPRINT_L1,
		    ibmf_saa_send_informinfo, IBMF_TNF_ERROR, "",
		    "ibmf_saa_send_informinfo: %s, ibmf_status = %d\n",
		    tnf_string, msg, "ibmf_saa_impl_send_request() failed",
		    tnf_int, ibmf_status, res);

		res = 0;

	} else {

		IBMF_TRACE_1(IBMF_TNF_DEBUG, DPRINT_L3,
		    ibmf_saa_send_informinfo, IBMF_TNF_TRACE, "",
		    "ibmf_saa_send_informinfo: %s\n", tnf_string, msg,
		    "Request sent successfully");

		res = 1;

		/*
		 * if this was an asynchronous transaction (not the unsequenced
		 * unsubscribe case) return here.
		 * The callback will clean up everything.
		 */
		if (unseq_unsubscribe == B_FALSE) {

			goto bail;
		}
	}

	kmem_free(trans_info, sizeof (saa_impl_trans_info_t));

	mutex_enter(&saa_portp->saa_pt_kstat_mutex);

	IBMF_SAA_SUB32_KSTATS(saa_portp, outstanding_requests, 1);
	IBMF_SAA_ADD32_KSTATS(saa_portp, failed_requests, 1);

	mutex_exit(&saa_portp->saa_pt_kstat_mutex);

	/*
	 * if subscribe transaction failed, update status mask
	 * to indicate "response"
	 */
	if ((res == 0) && (subscribe == B_TRUE)) {

		mutex_enter(&saa_portp->saa_pt_event_sub_mutex);

		saa_portp->saa_pt_event_sub_arrive_mask = 0;
		saa_portp->saa_pt_event_sub_success_mask = 0;

		switch (producer_type) {

			case MAD_INFORMINFO_NODETYPE_CA:
				producer_type_mask =
				    IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_CA;
			break;
			case MAD_INFORMINFO_NODETYPE_SWITCH:
				producer_type_mask =
				    IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_SWITCH;
			break;
			case MAD_INFORMINFO_NODETYPE_ROUTER:
				producer_type_mask =
				    IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_ROUTER;
			break;
			case MAD_INFORMINFO_NODETYPE_SUBNET_MANAGEMENT:
				producer_type_mask =
				    IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_SM;
			break;
			default:
				IBMF_TRACE_2(IBMF_TNF_NODEBUG, DPRINT_L1,
				    ibmf_saa_send_informinfo, IBMF_TNF_ERROR,
				    "", "ibmf_saa_send_informinfo: %s, "
				    "producer_type = 0x%x\n",
				    tnf_string, msg, "Unknown producer type",
				    tnf_opaque, producer_type, producer_type);

				ASSERT(0);
				producer_type_mask = 0;
			break;
		}

		saa_portp->saa_pt_event_sub_arrive_mask |= producer_type_mask;

		mutex_exit(&saa_portp->saa_pt_event_sub_mutex);
	}

bail:
	IBMF_TRACE_1(IBMF_TNF_DEBUG, DPRINT_L3,
	    ibmf_saa_send_informinfo_end, IBMF_TNF_TRACE, "",
	    "ibmf_saa_send_informinfo() exit: result = 0x%x\n",
	    tnf_opaque, result, res);

	return (res);
}

/*
 * ibmf_saa_informinfo_cb:
 *
 * Called when the asynchronous informinfo request receives its response.
 * Checks the status (whether the ibmf_saa was able to subscribe with the SA for
 * events) and updates the status mask for the specific producer.  If all four
 * producer types have arrived then the event clients are notified if there has
 * been a change in the status.
 *
 * Input Arguments
 * arg		user-specified pointer (points to the current port data)
 * length	length of payload returned (should be size of informinfo_rec)
 * buffer	pointer to informinfo response returned (should not be null)
 * status	status of sa access request
 * producer_type for subscriptions, indicates the notice producer type that was
 * 		requested; ignored for unsubscribes
 *
 * Output Arguments
 * none
 *
 * Returns void
 */
static void
ibmf_saa_informinfo_cb(void *arg, size_t length, char *buffer,
    int status, uint32_t producer_type)
{
	saa_port_t			*saa_portp;
	uint8_t				producer_type_mask;
	boolean_t			notify_clients;
	uint8_t				event_status_mask;
	ibmf_saa_event_details_t	event_details;

	IBMF_TRACE_2(IBMF_TNF_DEBUG, DPRINT_L3, ibmf_saa_informinfo_cb_start,
	    IBMF_TNF_TRACE, "", "ibmf_saa_informinfo_cb() enter: producer_type "
	    "= 0x%x, status = %d\n", tnf_opaque, producer_type, producer_type,
	    tnf_int, status, status);

	saa_portp = (saa_port_t *)arg;

	notify_clients = B_FALSE;

	/* if producer type is 0 this was an unsubscribe */
	if (producer_type == 0) {

		IBMF_TRACE_1(IBMF_TNF_DEBUG, DPRINT_L3,
		    ibmf_saa_informinfo_cb, IBMF_TNF_TRACE, "",
		    "ibmf_saa_informinfo_cb(): %s",
		    tnf_string, msg, "handling unsubscribe");

		if (buffer != NULL)
			kmem_free(buffer, length);

		goto bail;
	}

	/* determine which event it was */
	switch (producer_type) {

		case MAD_INFORMINFO_NODETYPE_CA:
			producer_type_mask =
			    IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_CA;
		break;
		case MAD_INFORMINFO_NODETYPE_SWITCH:
			producer_type_mask =
			    IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_SWITCH;
		break;
		case MAD_INFORMINFO_NODETYPE_ROUTER:
			producer_type_mask =
			    IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_ROUTER;
		break;
		case MAD_INFORMINFO_NODETYPE_SUBNET_MANAGEMENT:
			producer_type_mask =
			    IBMF_SAA_EVENT_STATUS_MASK_PRODUCER_SM;
		break;

		default:
			IBMF_TRACE_2(IBMF_TNF_NODEBUG, DPRINT_L1,
			    ibmf_saa_send_informinfo_cb, IBMF_TNF_ERROR,
			    "", "ibmf_saa_informinfo_cb: %s, "
			    "producer_type = 0x%x\n",
			    tnf_string, msg, "Unknown producer type",
			    tnf_opaque, producer_type, producer_type);

			producer_type_mask = 0;
		break;
	}

	mutex_enter(&saa_portp->saa_pt_event_sub_mutex);

	if (saa_portp->saa_pt_event_sub_arrive_mask & producer_type_mask) {

		mutex_exit(&saa_portp->saa_pt_event_sub_mutex);

		IBMF_TRACE_2(IBMF_TNF_DEBUG, DPRINT_L2,
		    ibmf_saa_informinfo_cb, IBMF_TNF_TRACE, "",
		    "ibmf_saa_informinfo_cb(): %s, prod_type_mask = 0x%x",
		    tnf_string, msg, "Received duplicate response",
		    tnf_opaque, producer_type_mask, producer_type_mask);

		if (buffer != NULL)
			kmem_free(buffer, length);

		goto bail;
	}

	saa_portp->saa_pt_event_sub_arrive_mask |= producer_type_mask;

	/* process response */
	if ((status != IBMF_SUCCESS) || (buffer == NULL)) {

		IBMF_TRACE_4(IBMF_TNF_NODEBUG, DPRINT_L1,
		    ibmf_saa_informinfo_cb, IBMF_TNF_ERROR, "",
		    "ibmf_saa_informinfo_cb: %s, status = %d,"
		    " buffer = 0x%p, length = %d\n",
		    tnf_string, msg, "could not get informinfo resp",
		    tnf_int, status, status, tnf_opaque, buffer, buffer,
		    tnf_uint, length, length);

	} else if (buffer != NULL) {

		kmem_free(buffer, length);
		saa_portp->saa_pt_event_sub_success_mask |= producer_type_mask;
	}

	/* if all four InformInfo responses have arrived */
	if (saa_portp->saa_pt_event_sub_arrive_mask ==
	    IBMF_SAA_PORT_EVENT_SUB_ALL_ARRIVE) {

		IBMF_TRACE_3(IBMF_TNF_DEBUG, DPRINT_L3,
		    ibmf_saa_informinfo_cb, IBMF_TNF_TRACE, "",
		    "ibmf_saa_informinfo_cb(): %s, success mask = 0x%x,"
		    " last success mask = 0x%x\n",
		    tnf_string, msg, "all informinfo responses have arrived",
		    tnf_opaque, success_mask,
		    saa_portp->saa_pt_event_sub_success_mask,
		    tnf_opaque, last_success_mask,
		    saa_portp->saa_pt_event_sub_last_success_mask);

		mutex_enter(&saa_portp->saa_pt_mutex);

		/*
		 * Sending the four InformInfos is treated as one port client
		 * reference.  Now that all have returned decrement the
		 * reference count.
		 */
		ASSERT(saa_portp->saa_pt_reference_count > 0);
		saa_portp->saa_pt_reference_count--;

		mutex_exit(&saa_portp->saa_pt_mutex);

		if (saa_portp->saa_pt_event_sub_last_success_mask !=
		    saa_portp->saa_pt_event_sub_success_mask) {

			IBMF_TRACE_1(IBMF_TNF_DEBUG, DPRINT_L2,
			    ibmf_saa_informinfo_cb, IBMF_TNF_TRACE, "",
			    "ibmf_saa_informinfo_cb(): %s\n",
			    tnf_string, msg,
			    "success mask different - notifying clients");

			/*
			 * save status mask to give to clients and update last
			 * mask for next set of subscription requests
			 */
			event_status_mask =
			    saa_portp->saa_pt_event_sub_last_success_mask =
			    saa_portp->saa_pt_event_sub_success_mask;

			notify_clients = B_TRUE;
		}
	}

	mutex_exit(&saa_portp->saa_pt_event_sub_mutex);

	if (notify_clients == B_TRUE) {

		bzero(&event_details, sizeof (ibmf_saa_event_details_t));

		event_details.ie_producer_event_status_mask =
		    event_status_mask;

		ibmf_saa_notify_event_clients(saa_portp, &event_details,
		    IBMF_SAA_EVENT_SUBSCRIBER_STATUS_CHG, NULL);
	}

bail:
	IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
	    ibmf_saa_informinfo_cb_end,
	    IBMF_TNF_TRACE, "", "ibmf_saa_informinfo_cb() exit\n");
}

/*
 * ibmf_saa_notify_event_client_task
 *
 * Calls the event notification callback for a registered saa client.  Called
 * from ibmf_saa_notify_event_clients() for each client that has registered for
 * events.  ibmf_saa_notify_event_clients() will dispatch this task on the
 * saa_event_taskq so the client's callback can be invoked directly.
 *
 * Input Arguments
 * args			pointer to ibmf_saa_event_taskq_args_t
 *			this function will free memory associated with args
 *
 * Output Arguments
 * none
 *
 * Returns
 * void
 */
static void
ibmf_saa_notify_event_client_task(void *args)
{
	ibmf_saa_event_taskq_args_t	*event_taskq_args;
	saa_client_data_t		*client;

	IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L3,
	    ibmf_saa_notify_event_client_task_start,
	    IBMF_TNF_TRACE, "", "ibmf_saa_notify_event_client_task() enter\n");

	event_taskq_args = (ibmf_saa_event_taskq_args_t *)args;

	client = event_taskq_args->et_client;

	/* call client's callback (client pointer is ibmf_saa_handle) */
	(event_taskq_args->et_callback)((ibmf_saa_handle_t)client,
	    event_taskq_args->et_subnet_event,
	    event_taskq_args->et_event_details,
	    event_taskq_args->et_callback_arg);

	kmem_free(event_taskq_args->et_event_details,
	    sizeof (ibmf_saa_event_details_t));

	kmem_free(event_taskq_args, sizeof (ibmf_saa_event_taskq_args_t));

	/* decrement the callback count and signal a waiting client */
	mutex_enter(&client->saa_client_mutex);

	client->saa_client_event_cb_num_active--;

	if (client->saa_client_event_cb_num_active == 0) {

		cv_signal(&client->saa_client_event_cb_cv);

	}

	mutex_exit(&client->saa_client_mutex);

	IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L3,
	    ibmf_saa_notify_event_client_task_end,
	    IBMF_TNF_TRACE, "", "ibmf_saa_notify_event_client_task() exit\n");
}

/*
 * ibmf_saa_process_subnet_event:
 *
 * Called when the ibmf_saa is notified of a forwarded notice.  Converts the
 * notice into an ibmf_saa_event_details structure and calls
 * ibmf_saa_notify_event_clients() which will notify each interested client.
 *
 * Input Arguments
 * saa_portp		pointer to saa_port data
 * notice		notice that was forwarded from SA
 *
 * Output Arguments
 * none
 *
 * Returns
 * void
 */
static void
ibmf_saa_process_subnet_event(saa_port_t *saa_portp, ib_mad_notice_t *notice)
{
	ibmf_saa_event_details_t	event_details;
	sm_trap_64_t			trap_data_details;
	sm_trap_144_t			cap_mask_trap_data_details;
	sm_trap_145_t			sys_img_trap_data_details;
	ibmf_saa_subnet_event_t		subnet_event;

	IBMF_TRACE_1(IBMF_TNF_DEBUG, DPRINT_L3,
	    ibmf_saa_process_subnet_event_start,
	    IBMF_TNF_TRACE, "", "ibmf_saa_process_subnet_event() enter: "
	    "trap_number = 0x%x\n",
	    tnf_opaque, trap_number, notice->TrapNumber_DeviceID);

	bzero(&event_details, sizeof (ibmf_saa_event_details_t));

	/*
	 * fill in the appropriate fields of event_details depending on
	 * the trap number
	 */
	switch (notice->TrapNumber_DeviceID) {

		case SM_GID_IN_SERVICE_TRAP:

			ibmf_saa_gid_trap_parse_buffer(notice->DataDetails,
			    &trap_data_details);

			event_details.ie_gid = trap_data_details.GIDADDR;

			subnet_event = IBMF_SAA_EVENT_GID_AVAILABLE;
		break;


		case SM_GID_OUT_OF_SERVICE_TRAP:

			ibmf_saa_gid_trap_parse_buffer(notice->DataDetails,
			    &trap_data_details);

			event_details.ie_gid = trap_data_details.GIDADDR;

			subnet_event = IBMF_SAA_EVENT_GID_UNAVAILABLE;
		break;

		case SM_MGID_CREATED_TRAP:

			ibmf_saa_gid_trap_parse_buffer(notice->DataDetails,
			    &trap_data_details);

			event_details.ie_gid = trap_data_details.GIDADDR;

			subnet_event = IBMF_SAA_EVENT_MCG_CREATED;
		break;

		case SM_MGID_DESTROYED_TRAP:

			ibmf_saa_gid_trap_parse_buffer(notice->DataDetails,
			    &trap_data_details);

			event_details.ie_gid = trap_data_details.GIDADDR;

			subnet_event = IBMF_SAA_EVENT_MCG_DELETED;
		break;

		case SM_CAP_MASK_CHANGED_TRAP:

			ibmf_saa_capmask_chg_trap_parse_buffer(
			    notice->DataDetails, &cap_mask_trap_data_details);

			event_details.ie_lid =
			    cap_mask_trap_data_details.LIDADDR;
			event_details.ie_capability_mask =
			    cap_mask_trap_data_details.CAPABILITYMASK;

			subnet_event = IBMF_SAA_EVENT_CAP_MASK_CHG;
		break;

		case SM_SYS_IMG_GUID_CHANGED_TRAP:

			ibmf_saa_sysimg_guid_chg_trap_parse_buffer(
			    notice->DataDetails, &sys_img_trap_data_details);

			event_details.ie_lid =
			    sys_img_trap_data_details.LIDADDR;
			event_details.ie_sysimg_guid =
			    sys_img_trap_data_details.SYSTEMIMAGEGUID;

			subnet_event = IBMF_SAA_EVENT_SYS_IMG_GUID_CHG;
		break;

		default:
			/*
			 * do nothing if it's not one of the traps we care about
			 */
			IBMF_TRACE_1(IBMF_TNF_DEBUG, DPRINT_L3,
			    ibmf_saa_process_subnet_event_end,
			    IBMF_TNF_TRACE, "",
			    "ibmf_saa_process_subnet_event() exit: %s\n",
			    tnf_string, msg,
			    "not one of the six ibmf_saa subnet events");

			return;
	}

	ibmf_saa_notify_event_clients(saa_portp, &event_details, subnet_event,
	    NULL);

	IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
	    ibmf_saa_process_subnet_event_end,
	    IBMF_TNF_TRACE, "", "ibmf_saa_process_subnet_event() exit\n");
}

/*
 * ibmf_saa_notify_event_clients:
 *
 * Called when a trap for one of the six saa subnet events arrives or there is a
 * change in the status of event subscriptions.  Searches the list of clients
 * with callbacks and dispatches a taskq thread to notify the client that the
 * event occured.
 *
 * If some subscription request fails and a subsequent client registers for
 * events that client needs to know that it may not receive all events.  To
 * facilitate this, notify_event_clients() takes an optional parameter which
 * specifies a specific client.  If registering_client is non-NULL only this
 * client is notified.  If the parameter is NULL, all clients in the list are
 * notified.
 *
 * Input Arguments
 * saa_portp		pointer to saa_port data
 * event_details	pointer to ibmf_saa_event_details_t for this event
 * subnet_event		type of event that occured
 * registering_client	pointer to client_data_t if notification should go to a
 *			specific client; NULL if notification should go to all
 *			clients which subscribed for events
 *
 * Output Arguments
 * none
 *
 * Returns
 * none
 */
void
ibmf_saa_notify_event_clients(saa_port_t *saa_portp,
    ibmf_saa_event_details_t *event_details,
    ibmf_saa_subnet_event_t subnet_event, saa_client_data_t *registering_client)
{
	saa_client_data_t		*client;
	ibmf_saa_event_taskq_args_t	*event_taskq_args;
	int				status;

	IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
	    ibmf_saa_notify_event_clients_start,
	    IBMF_TNF_TRACE, "", "ibmf_saa_notify_event_clients() enter\n");

	mutex_enter(&saa_portp->saa_pt_event_sub_mutex);

	if (registering_client != NULL)
		client = registering_client;
	else
		client = saa_portp->saa_pt_event_sub_client_list;

	while (client != NULL) {

		_NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*event_taskq_args))

		event_taskq_args = kmem_zalloc(
		    sizeof (ibmf_saa_event_taskq_args_t), KM_NOSLEEP);
		if (event_taskq_args == NULL) {

			IBMF_TRACE_2(IBMF_TNF_NODEBUG, DPRINT_L1,
			    ibmf_saa_notify_event_clients_err, IBMF_TNF_ERROR,
			    "", "ibmf_saa_notify_event_clients: %s, client = "
			    "0x%x\n", tnf_string, msg,
			    "could not allocate memory for taskq args",
			    tnf_opaque, client, client);

			/*
			 * if a particular client was not specified continue
			 * processing the client list
			 */
			if (registering_client == NULL)
				client = client->next;
			else
				client = NULL;

			continue;
		}

		/*
		 * each task needs its own pointer, the task will free
		 * up this memory
		 */
		event_taskq_args->et_event_details = kmem_zalloc(
		    sizeof (ibmf_saa_event_details_t), KM_NOSLEEP);
		if (event_taskq_args->et_event_details == NULL) {

			IBMF_TRACE_2(IBMF_TNF_NODEBUG, DPRINT_L1,
			    ibmf_saa_notify_event_clients_err, IBMF_TNF_ERROR,
			    "", "ibmf_saa_notify_event_clients: %s, client = "
			    "0x%x\n", tnf_string, msg,
			    "could not allocate memory for taskq event details",
			    tnf_opaque, client, client);

			kmem_free(event_taskq_args,
			    sizeof (ibmf_saa_event_taskq_args_t));

			/*
			 * if a particular client was not specified continue
			 * processing the client list
			 */
			client =
			    (registering_client == NULL) ? client->next: NULL;

			continue;
		}

		mutex_enter(&client->saa_client_mutex);

		/*
		 * don't generate callbacks if client is not active
		 * (it's probably closing the session)
		 */
		if (client->saa_client_state != SAA_CLIENT_STATE_ACTIVE) {

			IBMF_TRACE_3(IBMF_TNF_DEBUG, DPRINT_L2,
			    ibmf_saa_notify_event_clients, IBMF_TNF_TRACE,
			    "", "ibmf_saa_notify_event_clients: %s, client = "
			    "0x%x, state = 0x%x\n", tnf_string, msg,
			    "client state not active",
			    tnf_opaque, client, client,
			    tnf_opaque, state, client->saa_client_state);

			mutex_exit(&client->saa_client_mutex);

			kmem_free(event_taskq_args->et_event_details,
			    sizeof (ibmf_saa_event_details_t));

			kmem_free(event_taskq_args,
			    sizeof (ibmf_saa_event_taskq_args_t));

			/*
			 * if a particular client was not specified continue
			 * processing the client list
			 */
			client =
			    (registering_client == NULL) ? client->next: NULL;

			continue;
		}

		/*
		 * increment the callback count so the client cannot close the
		 * session while callbacks are active
		 */
		client->saa_client_event_cb_num_active++;

		mutex_exit(&client->saa_client_mutex);

		event_taskq_args->et_client = client;
		event_taskq_args->et_subnet_event = subnet_event;

		bcopy(event_details, event_taskq_args->et_event_details,
		    sizeof (ibmf_saa_event_details_t));

		event_taskq_args->et_callback = client->saa_client_event_cb;
		event_taskq_args->et_callback_arg =
		    client->saa_client_event_cb_arg;

		_NOTE(NOW_VISIBLE_TO_OTHER_THREADS(*event_taskq_args))

		/* dispatch taskq thread to notify client */
		status = taskq_dispatch(saa_statep->saa_event_taskq,
		    ibmf_saa_notify_event_client_task, event_taskq_args,
		    KM_NOSLEEP);
		if (status == TASKQID_INVALID) {

			IBMF_TRACE_2(IBMF_TNF_NODEBUG, DPRINT_L1,
			    ibmf_saa_notify_event_clients_err, IBMF_TNF_ERROR,
			    "", "ibmf_saa_notify_event_clients: %s, client = "
			    "0x%x\n",
			    tnf_string, msg, "Could not dispatch event taskq",
			    tnf_opaque, client, client);

			kmem_free(event_taskq_args->et_event_details,
			    sizeof (ibmf_saa_event_details_t));

			kmem_free(event_taskq_args,
			    sizeof (ibmf_saa_event_taskq_args_t));

			/*
			 * decrement the callback count and signal a waiting
			 * client
			 */
			mutex_enter(&client->saa_client_mutex);

			client->saa_client_event_cb_num_active--;

			if (client->saa_client_event_cb_num_active == 0) {

				cv_signal(&client->saa_client_event_cb_cv);

			}

			mutex_exit(&client->saa_client_mutex);

		} else {

			IBMF_TRACE_2(IBMF_TNF_DEBUG, DPRINT_L3,
			    ibmf_saa_notify_event_clients_err, IBMF_TNF_ERROR,
			    "", "ibmf_saa_notify_event_clients: %s, client = "
			    "0x%x\n",
			    tnf_string, msg, "Dispatched task to notify client",
			    tnf_opaque, client, client);
		}


		/*
		 * if a particular client was not specified continue processing
		 * the client list
		 */
		client = (registering_client == NULL) ? client->next: NULL;
	}

	mutex_exit(&saa_portp->saa_pt_event_sub_mutex);

	IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
	    ibmf_saa_notify_event_clients_end,
	    IBMF_TNF_TRACE, "", "ibmf_saa_notify_event_clients() exit\n");
}

/*
 * ibmf_saa_report_cb:
 *
 * Called when a forwarded notice Report is received by ibmf_saa from the SA.
 * Converts the Report into an ib_mad_notice_t and calls
 * ibmf_saa_notify_event_clients() which will notify each subscribed ibmf_saa
 * client.  Also sends a response to the report to acknowledge to the SA that
 * this port is still up.
 *
 * This is the registered async callback with ibmf.  Only Reports should come
 * through this interface as all other transactions with ibmf_saa are sequenced
 * (ibmf_saa makes the initial request).
 *
 * This function cannot block since it is called from an ibmf callback.
 *
 * Input Arguments
 * ibmf_handle 			ibmf handle
 * msgp				pointer to ibmf_msg_t
 * args				pointer to saa_port data
 *
 * Output Arguments
 * none
 *
 * Returns
 * none
 */
void
ibmf_saa_report_cb(ibmf_handle_t ibmf_handle, ibmf_msg_t *msgp,
    void *args)
{
	ib_mad_hdr_t		*req_mad_hdr, *resp_mad_hdr;
	saa_port_t		*saa_portp, *saa_port_list_entry;
	ibmf_retrans_t		ibmf_retrans;
	int			ibmf_status;
	ib_mad_notice_t		*notice_report;
	saa_impl_trans_info_t	*trans_info;
	boolean_t		port_valid;
	uint16_t		mad_status;
	uint16_t		attr_id;
	boolean_t		response_sent = B_FALSE;
	size_t			length;
	int			status;

	IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
	    ibmf_saa_report_cb_start,
	    IBMF_TNF_TRACE, "", "ibmf_saa_report_cb() enter\n");

	_NOTE(ASSUMING_PROTECTED(*msgp))

	saa_portp = (saa_port_t *)args;

	port_valid = B_FALSE;

	/* check whether this portp is still valid */
	mutex_enter(&saa_statep->saa_port_list_mutex);

	saa_port_list_entry = saa_statep->saa_port_list;
	while (saa_port_list_entry != NULL) {

		if (saa_port_list_entry == saa_portp) {

			port_valid = ibmf_saa_is_valid(saa_portp, B_FALSE);

			break;
		}
		saa_port_list_entry = saa_port_list_entry->next;
	}

	mutex_exit(&saa_statep->saa_port_list_mutex);

	if (port_valid == B_FALSE) {

		IBMF_TRACE_2(IBMF_TNF_DEBUG, DPRINT_L2,
		    ibmf_saa_report_cb, IBMF_TNF_TRACE, "",
		    "ibmf_saa_report_cb: %s, saa_port = 0x%p\n",
		    tnf_string, msg, "port no longer valid",
		    tnf_opaque, saa_portp, saa_portp);

		goto bail;
	}

	req_mad_hdr = msgp->im_msgbufs_recv.im_bufs_mad_hdr;

	/* drop packet if status is bad */
	if ((msgp->im_msg_status != IBMF_SUCCESS) ||
	    (req_mad_hdr == NULL) ||
	    ((mad_status = b2h16(req_mad_hdr->Status)) != SA_STATUS_NO_ERROR)) {

		IBMF_TRACE_4(IBMF_TNF_DEBUG, DPRINT_L1,
		    ibmf_saa_report_cb, IBMF_TNF_TRACE, "",
		    "ibmf_saa_report_cb: %s, msg_status = 0x%x,"
		    " req_mad_hdr = 0x%p, mad_status = 0x%x\n",
		    tnf_string, msg, "Bad ibmf status",
		    tnf_opaque, msg_status, msgp->im_msg_status,
		    tnf_opaque, req_mad_hdr, req_mad_hdr,
		    tnf_opaque, mad_status,
		    (req_mad_hdr == NULL ? 0 : mad_status));

		goto bail;
	}

	/* drop packet if class version is not correct */
	if (req_mad_hdr->ClassVersion != SAA_MAD_CLASS_VERSION) {

		IBMF_TRACE_3(IBMF_TNF_DEBUG, DPRINT_L1,
		    ibmf_saa_report_cb, IBMF_TNF_TRACE, "",
		    "ibmf_saa_report_cb: %s, msg_class_ver = 0x%x,"
		    " ibmf_saa_class_ver = 0x%x\n",
		    tnf_string, msg, "Bad class version",
		    tnf_opaque, msg_class_ver, req_mad_hdr->ClassVersion,
		    tnf_opaque, ibmf_saa_class_ver, SAA_MAD_CLASS_VERSION);

		goto bail;
	}


	/*
	 * only care about notice reports(); should not get any other type
	 * of method or attribute
	 */
	if (((attr_id = b2h16(req_mad_hdr->AttributeID)) != SA_NOTICE_ATTRID) ||
	    (req_mad_hdr->R_Method != SA_SUBN_ADM_REPORT)) {

		IBMF_TRACE_3(IBMF_TNF_DEBUG, DPRINT_L2,
		    ibmf_saa_report_cb, IBMF_TNF_TRACE, "",
		    "ibmf_saa_report_cb: %s, attr_id = 0x%x, "
		    "method = 0x%x\n",
		    tnf_string, msg, "Unsolicited message not notice report",
		    tnf_opaque, attr_id, attr_id,
		    tnf_opaque, method, req_mad_hdr->R_Method);

		goto bail;
	}

	/*
	 * unpack the data into a ib_mad_notice_t; the data details are left
	 * as packed data and will be unpacked by process_subnet_event()
	 * is_get_resp parameter is set to B_TRUE since cl_data_len will
	 * probably be set to 200 bytes by ibmf (it's not an RMPP trans)
	 */
	status = ibmf_saa_utils_unpack_payload(
	    msgp->im_msgbufs_recv.im_bufs_cl_data,
	    msgp->im_msgbufs_recv.im_bufs_cl_data_len, SA_NOTICE_ATTRID,
	    (void **)&notice_report, &length, 0, B_TRUE, KM_NOSLEEP);
	if (status != IBMF_SUCCESS) {

		IBMF_TRACE_2(IBMF_TNF_DEBUG, DPRINT_L2,
		    ibmf_saa_report_cb, IBMF_TNF_TRACE, "",
		    "ibmf_saa_report_cb: %s, status = %d",
		    tnf_string, msg, "Could not unpack data",
		    tnf_int, status, status);

		goto bail;
	}

	ASSERT(length == sizeof (ib_mad_notice_t));

	ibmf_saa_process_subnet_event(saa_portp, notice_report);

	kmem_free(notice_report, length);

	_NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*resp_mad_hdr))

	/* send ReportResp */
	resp_mad_hdr = kmem_zalloc(sizeof (ib_mad_hdr_t), KM_SLEEP);

	bcopy(req_mad_hdr, resp_mad_hdr, sizeof (ib_mad_hdr_t));

	resp_mad_hdr->R_Method = SA_SUBN_ADM_REPORT_RESP;

	msgp->im_msgbufs_send.im_bufs_mad_hdr = resp_mad_hdr;
	msgp->im_msgbufs_send.im_bufs_cl_hdr = kmem_zalloc(
	    msgp->im_msgbufs_recv.im_bufs_cl_hdr_len, KM_SLEEP);
	msgp->im_msgbufs_send.im_bufs_cl_hdr_len =
	    msgp->im_msgbufs_recv.im_bufs_cl_hdr_len;

	/* only headers are needed */
	msgp->im_msgbufs_send.im_bufs_cl_data = NULL;
	msgp->im_msgbufs_send.im_bufs_cl_data_len = 0;

	/*
	 * report_cb cannot block because it's in the context of an ibmf
	 * callback.  So the response needs to be sent asynchronously.
	 * ibmf_saa_async_cb is an appropriate callback to use for the response.
	 * Set up a trans_info structure as saa_async_cb expects.  But don't use
	 * ibmf_saa_impl_send_request() to send the response since that function
	 * does unncessary steps in this case (like allocating a new ibmf msg).
	 * Only the si_trans_port field needs to be filled in.
	 */
	trans_info = kmem_zalloc(sizeof (saa_impl_trans_info_t), KM_NOSLEEP);
	if (trans_info == NULL) {

		IBMF_TRACE_1(IBMF_TNF_NODEBUG, DPRINT_L1,
		    ibmf_saa_report_cb_err, IBMF_TNF_TRACE, "",
		    "ibmf_saa_report_cb: %s",
		    tnf_string, msg, "could not allocate trans_info structure");

		goto bail;
	}

	trans_info->si_trans_port = saa_portp;

	mutex_enter(&saa_portp->saa_pt_mutex);

	bcopy(&saa_portp->saa_pt_ibmf_retrans, &ibmf_retrans,
	    sizeof (ibmf_retrans_t));

	saa_portp->saa_pt_num_outstanding_trans++;

	mutex_exit(&saa_portp->saa_pt_mutex);

	ASSERT(ibmf_handle == saa_portp->saa_pt_ibmf_handle);

	ibmf_status = ibmf_msg_transport(ibmf_handle,
	    saa_portp->saa_pt_qp_handle, msgp, &ibmf_retrans, ibmf_saa_async_cb,
	    trans_info, 0);
	if (ibmf_status != IBMF_SUCCESS) {

		IBMF_TRACE_2(IBMF_TNF_DEBUG, DPRINT_L1,
		    ibmf_saa_report_cb, IBMF_TNF_TRACE, "",
		    "ibmf_saa_report_cb: %s, msg_status = 0x%x\n",
		    tnf_string, msg, "Could not send report response",
		    tnf_int, ibmf_status, ibmf_status);

		mutex_enter(&saa_portp->saa_pt_mutex);

		ASSERT(saa_portp->saa_pt_num_outstanding_trans > 0);
		saa_portp->saa_pt_num_outstanding_trans--;

		mutex_exit(&saa_portp->saa_pt_mutex);

		kmem_free(trans_info, sizeof (saa_impl_trans_info_t));

	} else {

		IBMF_TRACE_1(IBMF_TNF_DEBUG, DPRINT_L3,
		    ibmf_saa_report_cb, IBMF_TNF_TRACE, "",
		    "ibmf_saa_report_cb: %s\n",
		    tnf_string, msg, "Asynchronous Report response sent");

		response_sent = B_TRUE;
	}

bail:
	if (response_sent == B_FALSE) {
		ibmf_status = ibmf_free_msg(ibmf_handle, &msgp);
		ASSERT(ibmf_status == IBMF_SUCCESS);
	}

	IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
	    ibmf_saa_informinfo_cb_end, IBMF_TNF_TRACE, "",
	    "ibmf_saa_report_cb() exit\n");
}

/*
 * ibmf_saa_add_event_subscriber:
 *
 * Adds an interested client to the list of subscribers for events for a port.
 * If it's the first client, generates the subscription requests.
 * This function must only be called if event_args is not null
 *
 * Input Arguments
 *
 * client		pointer to client data (client->saa_port should be set)
 * event_args		pointer to event_args passed in from client (non-NULL)
 *
 * Output Arguments
 * none
 *
 * Returns
 * void
 */
void
ibmf_saa_add_event_subscriber(saa_client_data_t *client,
    ibmf_saa_subnet_event_args_t *event_args)
{
	saa_port_t			*saa_portp;
	boolean_t			first_client;
	uint8_t				producer_status_mask;
	ibmf_saa_event_details_t	event_details;

	IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
	    ibmf_saa_add_event_subscriber_start, IBMF_TNF_TRACE, "",
	    "ibmf_saa_add_event_subscriber() enter\n");

	/* event_args should be checked before calling this function */
	ASSERT(event_args != NULL);

	/* don't add client if no callback function is specified */
	if (event_args->is_event_callback == NULL)
		return;

	saa_portp = client->saa_client_port;

	client->saa_client_event_cb = event_args->is_event_callback;
	client->saa_client_event_cb_arg = event_args->is_event_callback_arg;

	/*
	 * insert this client onto the list; this list is used when a
	 * Report arrives to call each client's callback
	 */
	mutex_enter(&saa_portp->saa_pt_event_sub_mutex);

	IBMF_TRACE_2(IBMF_TNF_DEBUG, DPRINT_L3,
	    ibmf_sa_session_open, IBMF_TNF_TRACE, "",
	    "ibmf_saa_add_event_subscriber: %s, client = 0x%x\n",
	    tnf_string, msg, "Adding client to event subscriber list",
	    tnf_opaque, client, client);

	if (saa_portp->saa_pt_event_sub_client_list == NULL)
		first_client = B_TRUE;
	else {
		first_client = B_FALSE;
		producer_status_mask =
		    saa_portp->saa_pt_event_sub_last_success_mask;
	}

	client->next = saa_portp->saa_pt_event_sub_client_list;
	saa_portp->saa_pt_event_sub_client_list = client;

	mutex_exit(&saa_portp->saa_pt_event_sub_mutex);

	if (first_client == B_TRUE) {

		/*
		 * increment the reference count by one to account for
		 * the subscription requests.  All four InformInfo's are
		 * sent as one port client reference.
		 */
		mutex_enter(&saa_portp->saa_pt_mutex);

		saa_portp->saa_pt_reference_count++;

		mutex_exit(&saa_portp->saa_pt_mutex);

		/* subscribe for subnet events */
		ibmf_saa_subscribe_events(saa_portp, B_TRUE, B_FALSE);

	} else if (producer_status_mask != IBMF_SAA_PORT_EVENT_SUB_ALL_ARRIVE) {

		/*
		 * if this is not the first client and the producer status mask
		 * is not all success, generate a callback to indicate to the
		 * client that not all events will be forwarded
		 */
		bzero(&event_details, sizeof (ibmf_saa_event_details_t));

		event_details.ie_producer_event_status_mask =
		    producer_status_mask;

		ibmf_saa_notify_event_clients(saa_portp, &event_details,
		    IBMF_SAA_EVENT_SUBSCRIBER_STATUS_CHG, client);
	}

	IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
	    ibmf_saa_add_event_subscriber_end, IBMF_TNF_TRACE, "",
	    "ibmf_saa_add_event_subscriber() exit\n");
}
